#ifndef _hd44780_binary_lib_
#define _hd44780_binary_lib_

/**
 * settings delays global
 * MC14555 operate time - 440 ns
 * HCF4017BE operate time -
 * 	next pulse width - npw = 200ns
 * 	propagation next delay time - pndt = 650 ns
 * 	reset pulse width - rsw = 260 ns
 * 	propagations reset delay time - prst = 530 ns
 * CD4081BE operate time - 250 ns
 * hd44780 operate time - see doc, RTFD ;)
 * if you have errors, try increasing those values (depens on errors you have)
 * Enable_PULSE_WIDTH is a strange value. enable Pulse cant be shorter then 230 ns 
 * and also cant be longer then 500ns. Thats why incriesing ENABLE_PULSE_WIDTH to for exmaple
 * ndelay(1200) will affect in VERY LOW characters writing. For avery single character written 
 * it checked busyflag 10 times or more. The value of ndelay(820) is a safe value, lcds 
 * operate extreamly fast and i havent seen an error.
 * For delays with NEXT and RESET i added 200 ns to make sure, errors isnt their fault 
 */
#define ADDRESS_SET_UP_TIME() 	ndelay(140)  /* 40 ns for hd44780 + 100*/
#define ENABLE_PULSE_WIDTH() 	ndelay(820) /* 4555 + 4081 + 230 ns for hd44780 - 100 */
#define DATA_HOLD_TIME() 	ndelay(780) /* 4555 + 4081 + 10ns for hd44780 + 100 */
#define NEXT_PULSE_WIDTH() 	ndelay(840) /* 4555 + npw 4017 + 200 */
#define NEXT_SET_UP_TIME() 	ndelay(1190) /* 4555 + npw 4017 + pndt + 200 */
#define RESET_PULSE_WIDTH() 	ndelay(1200) /* 4555 + rsw 4017 + 200 */
#define RESET_SET_UP_TIME() 	ndelay(1270)  /* 4555 + prst 4017 + 200 */
/**
 * settings * delays when no busyflag checkig 
 * Minimum time to wait after most commands is 39us (for safe i sleep 50us)
 * Clear Display and Return Home need to wait 1.53ms(for safe i sleep 16ms)
 * LOW_VOLTAGE displays operate much slower: normal delay ~160u and long delay ~20ms
 * those values will be used if you comment the busyflag checking section in write and read functions
 * this sleep wil use ppl who cannot read from devices.
 * remember that if you do not check the busyflag,
 *   then sleeps _should_ last longer then in the table in the documentation (so says documentation! xD )
 */
#define LONG_DELAY()			\
	set_current_state(TASK_INTERRUPTIBLE); 	\
	schedule_timeout( (int)( (16 * HZ) / 10000 ) )
#define NORMAL_DELAY() udelay(50)

/**
 * settings * delays associated with busyflag checking 
 * sometimes when the cable is work, busyflag is not checked and it stucks into
 * an infinity loop (wait for busyflag to be low, but couse od no connection
 * it stays high), thats why i added BUSYFLAG_STUCK, it quits checking BF after 
 * so many times it checked, and unsets BF checking 
 * */
#define BUSYFLAG_DELAY() 	ndelay(1) /* the delay between BF checks */
#define BUSYFLAG_STUCK 		10000 /* how many times check busyflag to get stuck? */

/** global functions, better use them this way */
#define hd44780_write_inst(ctrl, inst)	hd44780_write(ctrl, RS_INST, inst)
#define hd44780_write_data(ctrl, data)	hd44780_write(ctrl, RS_DATA, data)
#define hd44780_write_inst_all(inst)	hd44780_write_all(RS_INST, inst)
#define hd44780_write_data_all(data)	hd44780_write_all(RS_DATA, data)
#define hd44780_read_inst(ctrl)		hd44780_read(ctrl, RS_INST)
#define hd44780_read_data(ctrl)		hd44780_read(ctrl, RS_DATA)

/* local macros */
#ifdef DEBUG_HD44780
#	define	PDEBUG_hd44780(fmt, ...) printk("lcdmod: hd44780: " fmt, ##__VA_ARGS__)
#else
#	define	PDEBUG_hd44780(fmt, ...)
#endif
/* macros like lp.h, using parport_pc, usefull, remember that they use struct phcdev! */
#define r_dtr()		(parport_pc_read_data(phcdev.pd->port))
#define r_ctr()		(parport_pc_read_control(phcdev.pd->port))
#define r_str()		(parport_pc_read_status(phcdev.pd->port))
#define w_dtr(y)	(parport_pc_write_data(phcdev.pd->port, (y)))
#define w_ctr(y)	(parport_pc_write_control(phcdev.pd->port, (y)))


/* is_inst - returns 1 when RS_INST flag is set ( RS_INST or RS_DATA must be != 0 ); similar is_data */
#if RS_DATA != 0
#	define is_inst(x)	(~(x & RS_DATA))
#	define is_data(x)	  (x & RS_DATA)
#elif RS_INST != 0
#	define is_inst(x) 	  (x & RS_INST)
#	define is_data(x)	(~(x & RS_INST))
#else
#	error "Both RS_INST and RS_DATA are != 0"
#	error "One of it needs to be != 0"
#	error "Get back in the source and correct it"
#endif

/* funtions */
#define _hd44780_sleep_on_write_(flags, command) \
do {\
	if ( is_inst(flags) ) { \
		PDEBUG_hd44780(" ^ inst write sleep on cmd: %x\n", command);\
		switch (command) {\
		case 0x01:\
		case 0x02:\
		case 0x03:\
			LONG_DELAY();\
			break;\
		default:\
			NORMAL_DELAY();\
			break;\
		}\
	} else {\
		PDEBUG_hd44780(" ^ data write sleep on cmd: %x\n", command);\
		NORMAL_DELAY(); \
	}\
} while(0)

#define _hd44780_sleep_on_read_(flags) \
do {\
	if ( is_inst(flags) ) {\
		PDEBUG_hd44780(" ^ busyflag read sleep.\n");\
	} else {\
		PDEBUG_hd44780(" ^ data read sleep.\n");\
		NORMAL_DELAY();\
	}\
} while(0)
/* resets the counter, sets base to 0 */
#define _counter_reset_()\
do {\
	PDEBUG_hd44780(" ^ counter reset on port %d \n", base);\
	base = 0;\
	++phcdev.reset_num; \
	w_ctr(RW_READ | RS_INST | CNT_NEXT);\
	w_ctr(RW_READ | RS_INST | CNT_RESET);\
	RESET_PULSE_WIDTH();\
	w_ctr(RW_READ | RS_INST | CNT_OFF);\
	RESET_SET_UP_TIME();\
} while(0)
/* sets active controler to ctrl_nr, sets base to ctrl_nr */
#define _hd44780_set_active_ctrl_(ctrl_nr) \
do {\
	if (ctrl_nr < base) {\
		_counter_reset_();\
	}\
	while(base != ctrl_nr) {\
		PDEBUG_hd44780(" ^ loop: %d ++ up to %d \n", base, ctrl_nr);\
		++phcdev.next_num; \
		w_ctr( RW_READ | RS_INST | CNT_NEXT);\
		NEXT_PULSE_WIDTH();\
		w_ctr( RW_READ | RS_INST | CNT_OFF);\
		NEXT_SET_UP_TIME();\
		if (base == CNT_LENGTH-1)\
			base = 0;\
		else \
			++base;\
	}\
} while(0)

/* sets the flags/data for the controler */
static void _hd44780_capable_(int flags)
{
	w_ctr(flags | CNT_OFF);
	ADDRESS_SET_UP_TIME();
}
/* enables controler */
static void _hd44780_enable_(int flags)
{
	w_ctr(flags | CNT_ENABLE);
	ENABLE_PULSE_WIDTH();
}
/* disables controler */
static void _hd44780_disable_(int flags)
{
	w_ctr(flags | CNT_OFF);
	DATA_HOLD_TIME();
}


/** _hd44780_wait_for_busyflag_
 * wait for busyflag to go low on setted controler
 * mutex is locked, counter is set
 */
static void _hd44780_wait_for_busyflag_(void)
{
	/* here could be cursor getting/checking */
	/* remember to run hd44780_capable before this check */
	unsigned char status;
	unsigned int count;
	PDEBUG_hd44780(" ^ waiting for ctrl: %d o be ready to operate with.\n", ctrl_nr);
	_hd44780_capable_(RW_READ | RS_INST);
	for(count = 0, status = 0; count < BUSYFLAG_STUCK && status; ++count) {
		++phcdev.bfchecks_num;
		_hd44780_enable_(RW_READ | RS_INST);
		status = (r_dtr() >> 7);
		_hd44780_disable_(RW_READ | RS_INST);
		BUSYFLAG_DELAY();
	}
	if (count == BUSYFLAG_STUCK) {
		PWARN("Reading busyflag ctrl %d stuck: checked %d times!", base, BUSYFLAG_STUCK);
		PWARN("Disabling busyflag checking on this port!\n");
		set_bit(FLAG_NOBFCHECK, &(phcdev.flags));
	}
}
/** 
 * waits for specyfic hd44780 to be ready to write to 
 * mutex is locked, counter is set 
 */
static void _hd44780_wait_for_ready_(void)
{ 
}
/**
 * init the port counter and maybe other stuff 
 */
static void port_init(void)
{
	mutex_lock(&hd44780_mutex);
	w_dtr(0x00);
	parport_data_reverse(phcdev.pd->port);
	/* reset the counter 
	 * from now on, base is the value of the active controler 
	 */
	_counter_reset_();
	mutex_unlock(&hd44780_mutex);
}

static unsigned char hd44780_read(unsigned int ctrl_nr, int flags)
{
	/* Be aware of bugs in the following code. 
	 * I written it and have proved it correct, not tried it
	 * passing O_DIRECT flag to oflags in reading from char device
	 * should indicate using this function to read from lcds.
	 * it could be really fun using it :) for checking errors
	 */
	unsigned char status = 0;
	PDEBUG_hd44780("write: %d %s act %d phcdev->flags:%lx\n",
		ctrl_nr, flags ? "inst" : "data", base, phcdev.flags);
	if (ctrl_nr >= ctrls_num) return -1;
	if (base >= CNT_LENGTH) return -1;
	flags |= RW_READ;
	/** prepare port and ctrl */
	if (test_bit(FLAG_NOBFCHECK, &(phcdev.flags))) {
		_hd44780_wait_for_busyflag_();
	} else {
		_hd44780_sleep_on_read_(flags);
	}
	mutex_lock(&hd44780_mutex);
	_hd44780_set_active_ctrl_(ctrl_nr);
	_hd44780_wait_for_ready_();
	/** read data */
	++phcdev.read_num;
	parport_data_reverse(phcdev.pd->port);
	_hd44780_capable_(flags);
	_hd44780_enable_(flags);
	status=r_dtr();
	_hd44780_disable_(flags);
	mutex_unlock(&hd44780_mutex);
	return status;
}

static void hd44780_write(unsigned int ctrl_nr, int flags, unsigned char command)
{
	PDEBUG_hd44780("write: %d %s %#2x act %d phcdev->flags:%lx\n",
		ctrl_nr, flags ? "inst" : "data", command, base, phcdev.flags);
	if (base >= CNT_LENGTH) return;
	if (ctrl_nr >= ctrls_num) return;
	flags = flags | RW_WRITE;
	/** prepare port and ctrl */
	mutex_lock(&hd44780_mutex);
	_hd44780_set_active_ctrl_(ctrl_nr);
	if (test_bit(FLAG_NOBFCHECK, &(phcdev.flags))) {
		_hd44780_wait_for_ready_();
	} else {
		_hd44780_sleep_on_write_(flags, command);
	}
	/** write data */
	++phcdev.write_num;
	parport_data_forward(phcdev.pd->port);
	w_dtr(command);
	_hd44780_capable_(flags);
	_hd44780_enable_(flags);
	_hd44780_disable_(flags);
	w_dtr(0x00);
	
	parport_data_reverse(phcdev.pd->port);
	mutex_unlock(&hd44780_mutex);
}

static inline void hd44780_write_all(int flags, unsigned char command)
{
	unsigned int i;
	for(i = 0; i < ctrls_num; i++) {
		hd44780_write(i, flags, command);
	}
}


#endif /*  _hd44780_binary_lib_ */

